package cn.jetpiece.cloud.joss.app.modular.storage.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.jetpiece.cloud.core.util.HttpContext;
import cn.jetpiece.cloud.joss.api.object.model.enums.AclEnum;
import cn.jetpiece.cloud.joss.api.object.model.params.CompleteMultiPartUploadObject;
import cn.jetpiece.cloud.joss.api.object.model.params.InitMultiPartUploadObject;
import cn.jetpiece.cloud.joss.api.object.model.params.MultiPartUploadObject;
import cn.jetpiece.cloud.joss.api.object.model.result.JossPartEtag;
import cn.jetpiece.cloud.joss.api.object.model.result.UploadObjectResult;
import cn.jetpiece.cloud.joss.app.modular.basics.entity.StorageClass;
import cn.jetpiece.cloud.joss.app.modular.object.entity.Bucket;
import cn.jetpiece.cloud.joss.app.modular.object.entity.Provider;
import cn.jetpiece.cloud.joss.app.modular.object.entity.Region;
import cn.jetpiece.cloud.joss.app.modular.object.enums.BasicExceptionEnum;
import cn.jetpiece.cloud.joss.app.modular.object.model.BucketInfo;
import cn.jetpiece.cloud.joss.app.modular.storage.service.AbstractJossClient;
import cn.jetpiece.cloud.model.exception.ServiceException;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.minio.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.parser.Ranges;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class MinioJossClient extends AbstractJossClient {
    private final MinioClient client;

    public MinioJossClient(String endpoint, String accessKey, String secretKey) {
        super(endpoint, accessKey, secretKey);
        client = MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
    }

    @Override
    public void createBucket(Provider provider, Region region, Bucket bucket, StorageClass storageClass) {
        try {
            client.makeBucket(MakeBucketArgs.builder().bucket(bucket.getBucketName()).build());
//            if (bucket.getAcl() == AclEnum.PRIVATE.getCode()) {
//                String none = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[],\"Resource\":[\"arn:aws:s3:::" + bucket.getBucketName() + "/*\"]}]}";
//                client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucket.getBucketName()).config(none).build());
//            } else if (bucket.getAcl() == AclEnum.PUBLIC_READ.getCode()) {
//                String readOnly = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\"],\"Resource\":[\"arn:aws:s3:::" + bucket.getBucketName() + "\"]}]}";
//                client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucket.getBucketName()).config(readOnly).build());
//            } else if (bucket.getAcl() == AclEnum.PUBLIC_READ_WRITE.getCode()) {
//                String readWrite = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + bucket.getBucketName() + "\"]}]}";
//                client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucket.getBucketName()).config(readWrite).build());
//            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(BasicExceptionEnum.BUCKET_CREATE_ERROR);
        }
    }

    @Override
    public void deleteBucket(BucketInfo bucketInfo) {
        try {
            client.removeBucket(RemoveBucketArgs.builder().bucket(bucketInfo.getBucketName()).build());
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(BasicExceptionEnum.OBJECT_DELETE_ERROR);
        }
    }

    @Override
    public String initMultipartUpload(BucketInfo bucketInfo, String objectName, InitMultiPartUploadObject param) {
        throw new ServiceException(BasicExceptionEnum.PROVIDER_NOT_SUPPORT_API);
    }

    @Override
    public JossPartEtag multipartUpload(BucketInfo bucketInfo, String objectName, MultiPartUploadObject param, MultipartFile file) {
        throw new ServiceException(BasicExceptionEnum.PROVIDER_NOT_SUPPORT_API);
    }

    @Override
    public UploadObjectResult completeMultipartUpload(BucketInfo bucketInfo, String objectName, CompleteMultiPartUploadObject param) {
        throw new ServiceException(BasicExceptionEnum.PROVIDER_NOT_SUPPORT_API);
    }

    @Override
    public String upload(BucketInfo bucketInfo, Integer acl, String objectName, File file) {
        if (acl == null) {
            acl = bucketInfo.getAcl();
        }
        if (objectName.startsWith("/")) {
            objectName = objectName.replaceFirst("/", "");
        }
        try {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(bucketInfo.getBucketName())
                    .object(objectName)
                    .contentType(HttpContext.getRequest().getContentType())
                    .stream(Files.newInputStream(file.toPath()), file.length(), -1L)
                    .build();
            client.putObject(putObjectArgs);
//            if (acl == AclEnum.PUBLIC_READ.getCode()) {
//                String readOnly = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\"],\"Resource\":[\"arn:aws:s3:::" + bucketInfo.getBucketName() + "\"]}," + "{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::" + bucketInfo.getBucketName() + "/" + objectName + "\"]}]}";
//                client.setBucketPolicy(SetBucketPolicyArgs.builder()
//                        .bucket(bucketInfo.getBucketName())
//                        .config(readOnly)
//                        .build());
//            } else if (acl == AclEnum.PUBLIC_READ_WRITE.getCode()) {
//                String readWrite = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + bucketInfo.getBucketName() + "\"]}," + "{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\"],\"Resource\":[\"arn:aws:s3:::" + bucketInfo.getBucketName() + "/" + objectName + "\"]}]}";
//                client.setBucketPolicy(SetBucketPolicyArgs.builder()
//                        .bucket(bucketInfo.getBucketName())
//                        .config(readWrite)
//                        .build());
//            }
            return bucketInfo.getRegion().getProtocol() + bucketInfo.getRegion().getInternetEndpoint() + "/" + bucketInfo.getBucketName() + "/" + objectName;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(BasicExceptionEnum.OBJECT_UPLOAD_ERROR);
        }
    }

    @Override
    public String upload(BucketInfo bucketInfo, Integer acl, String objectName, long objectSize, String contentType, byte[] objectBytes) {
        return upload(bucketInfo, acl, objectName, objectSize, contentType, new ByteArrayInputStream(objectBytes));
    }

    @Override
    public String upload(BucketInfo bucketInfo, Integer acl, String objectName, long objectSize, String contentType, InputStream inputStream) {
        if (acl == null) {
            acl = bucketInfo.getAcl();
        }
        if (objectName.startsWith("/")) {
            objectName = objectName.replaceFirst("/", "");
        }
        long partSize = objectSize < ObjectWriteArgs.MIN_MULTIPART_SIZE ? -1 : ObjectWriteArgs.MIN_MULTIPART_SIZE;
        try {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(bucketInfo.getBucketName())
                    .object(objectName)
                    .contentType(contentType)
                    .stream(inputStream, objectSize, partSize)
                    .build();
            client.putObject(putObjectArgs);
//            if (acl == AclEnum.PUBLIC_READ.getCode()) {
//                String readOnly = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::" + bucketInfo.getBucketName() + "/" + objectName + "\"]}]}";
//                client.setBucketPolicy(SetBucketPolicyArgs.builder()
//                        .bucket(bucketInfo.getBucketName())
//                        .config(readOnly)
//                        .build());
//            } else if (acl == AclEnum.PUBLIC_READ_WRITE.getCode()) {
//                String readWrite = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\"],\"Resource\":[\"arn:aws:s3:::" + bucketInfo.getBucketName() + "/" + objectName + "\"]}]}";
//                client.setBucketPolicy(SetBucketPolicyArgs.builder()
//                        .bucket(bucketInfo.getBucketName())
//                        .config(readWrite)
//                        .build());
//            }
            return bucketInfo.getRegion().getProtocol() + bucketInfo.getRegion().getInternetEndpoint() + "/" + bucketInfo.getBucketName() + "/" + objectName;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(BasicExceptionEnum.OBJECT_UPLOAD_ERROR);
        }
    }

    @Override
    public void delete(BucketInfo bucketInfo, String objectName, boolean checkAcl) {
        try {
            if (objectName.startsWith("/")) {
                objectName = objectName.replaceFirst("/", "");
            }
            if (checkAcl) {
                String policy = client.getBucketPolicy(GetBucketPolicyArgs.builder()
                        .bucket(bucketInfo.getBucketName() + "/" + objectName)
                        .build());
                if (policy == null) {
                    throw new ServiceException(BasicExceptionEnum.FORBIDDEN);
                }
                if (!policy.contains("DeleteObject")) {
                    throw new ServiceException(BasicExceptionEnum.FORBIDDEN);
                }
            }
            client.removeObject(RemoveObjectArgs.builder().bucket(bucketInfo.getBucketName()).object(objectName).build());
        } catch (ServiceException e) {
            throw e;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(BasicExceptionEnum.OBJECT_DELETE_ERROR);
        }
    }

    @Override
    public void getObject(BucketInfo bucketInfo, String objectName, boolean checkAcl) {
        HttpServletRequest request = HttpContext.getRequest();
        HttpServletResponse response = HttpContext.getResponse();
        if (objectName.startsWith("/")) {
            objectName = objectName.replaceFirst("/", "");
        }
        try {
//            if (checkAcl) {
//                String policy = client.getBucketPolicy(GetBucketPolicyArgs.builder()
//                        .bucket(bucketInfo.getBucketName())
//                        .build());
//                if (!hashPermission(policy, bucketInfo.getBucketName(), objectName, "s3:GetObject")) {
//                    throw new ServiceException(BasicExceptionEnum.FORBIDDEN);
//                }
//            }
            Ranges ranges = getRanges(request);
            StatObjectResponse objectStat = client.statObject(StatObjectArgs.builder()
                    .bucket(bucketInfo.getBucketName())
                    .object(objectName)
                    .build());
            if (ranges == null) {
                String ifNoneMatch = request.getHeader("If-None-Match");
                String ifModifiedSince = request.getHeader("If-Modified-Since");
                String etag = objectStat.etag();
                String lastModified = objectStat.lastModified().format(DateTimeFormatter.RFC_1123_DATE_TIME);
                response.setContentType(objectStat.contentType());
                response.setHeader("ETag", etag);
                response.setHeader("Last-Modified", lastModified);
                if (StrUtil.isAllNotBlank(ifNoneMatch, ifModifiedSince) && etag.equals(ifNoneMatch) && ifModifiedSince.equals(lastModified)) {
                    response.setContentLengthLong(0L);
                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                } else {
                    response.setHeader("Accept-Ranges", "bytes");
                    response.setContentLengthLong(objectStat.size());
                    GetObjectResponse getObjectResponse = client.getObject(GetObjectArgs.builder()
                            .bucket(bucketInfo.getBucketName())
                            .object(objectName)
                            .build());
                    copyIO(getObjectResponse, response.getOutputStream(), objectStat.size());
                }
            } else {
                long startRange = ranges.getEntries().get(0).getStart();
                long endRange = ranges.getEntries().get(0).getEnd();
                if (endRange == -1) {
                    endRange = objectStat.size() - 1;
                }
                Map<String, String> headers = new HashMap<>();
                headers.put("Range", "bytes=" + startRange + "-" + endRange);
                GetObjectResponse getObjectResponse = client.getObject(GetObjectArgs.builder()
                        .bucket(bucketInfo.getBucketName())
                        .object(objectName)
                        .extraHeaders(headers)
                        .build());

                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                response.setContentType(objectStat.contentType());
                response.setContentLengthLong(endRange - startRange + 1);
                response.setHeader("Accept-Ranges", "bytes");
                response.setHeader("Content-Range", ranges.getUnits() + " " + startRange + "-" + endRange + "/" + objectStat.size());
                response.setHeader("ETag", objectStat.etag());
                response.setHeader("Last-Modified", objectStat.lastModified().format(DateTimeFormatter.RFC_1123_DATE_TIME));
                copyIO(getObjectResponse, response.getOutputStream(), endRange - startRange + 1);
            }
        } catch (ServiceException e) {
            throw e;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(BasicExceptionEnum.OBJECT_PREVIEW_ERROR);
        }
    }

    @Override
    public void destroy() {
        log.info(client.toString() + " Close");
    }

    /**
     * 是否有对应的权限
     *
     * @param policy 权限字符串
     * @param bucket 桶名
     * @param prefix 前缀
     * @param action 操作
     * @return 结果
     */
    protected boolean hashPermission(String policy, String bucket, String prefix, String action) {
        JSONObject jsonObject = JSONObject.parseObject(policy);
        JSONArray jsonArray = jsonObject.getJSONArray("Statement");
        for (Object itemObj : jsonArray) {
            if (itemObj instanceof JSONObject) {
                JSONObject item = (JSONObject) itemObj;
                if (hashAction(item.getJSONArray("Action"), action) && hashResource(item.getJSONArray("Resource"), bucket, prefix)) {
                    if (item.getString("Effect").equals("Allow")) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * 是否有对应的操作权限
     *
     * @param actionArray 权限数据
     * @param action      权限
     * @return 结果
     */
    protected boolean hashAction(JSONArray actionArray, String action) {
        for (Object itemObj : actionArray) {
            if (itemObj.equals(action)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否有对应的资源
     *
     * @param resourceArray 资源数组
     * @param bucket        桶名
     * @param prefix        前缀
     * @return 结果
     */
    protected boolean hashResource(JSONArray resourceArray, String bucket, String prefix) {
        for (Object itemObj : resourceArray) {
            if (itemObj.equals("arn:aws:s3:::" + bucket + "/" + prefix)) {
                return true;
            }
        }
        return false;
    }
}
